#include <unistd.h>
#include <cassert>
#include <vector>
#include "pipe.hpp"
#include <string>
#include <sys/types.h>
#include <sys/wait.h>


const int ChildNum = 5;
Task t;

class EndPoint{

private:
    static int number ;
public:
    pid_t _child_pid;
    int _write_fd;
    string processname;

public:
    EndPoint(int id, int fd)
    :_child_pid(id),
    _write_fd(fd)
    {
        //要显示的效果：process-number[pid/fd]
        char namberbuffer[64];
        snprintf(namberbuffer,sizeof(namberbuffer),
        "process-%d[%d/%d]",number++,_child_pid,_write_fd);
        processname = namberbuffer;
    }

    string name()
    {
        return processname;
    }
    ~EndPoint()
    {

    }

};

int EndPoint::number = 0;

//子进程要执行的命令
void WaitCommand()
{
    //子进程死循环执行
    while(true)
    {
        int command = 0;//临时变量存储command
        int n = read(0,&command,sizeof(int));//read返回读取的字节数
        if(n == sizeof(int))
        {
            //读取成功，执行命令
            t.Execute(command);
        }
        else if(n == 0)
        {
            break;
        }
        else
        {
            break;
        }
    }
}

void CreateProcess(vector<EndPoint>* end_points)
{

    vector<int> fds;//每一个子进程，保存相应多余的文件描述符，好被关闭
    //循环创建5个子进程，后创建的子进程的文件描述符列表会包含指向旧管
    //道的文件描述符，在后续关闭子进程时会出现问题，先创建管道文件，因被后创建的子进程含有
    //指向其的文件描述符(为什么会含有呢？因为子进程是直接复制父进程的文件描述符表)。最老的管道文件
    //的引用计数为5，所以只关闭最初的父进程指向的文件描述符，对应子进程不会关闭相应的文件描述符
    //导致管道不能关闭。

    //创建5个子进程，ChildNum为全局常量，可直接在顶部改变其数量
    for(int i = 0 ; i < ChildNum ; i++)
    {
        //1.1创建管道
        int pipefd[2] = {0};
        int n = pipe(pipefd);//pipe的包含的头文件我怎么在“man pipe”中没找到呢，o(╥﹏╥)o
        assert( n == 0);//pipe创建成功后返回0，如果不是0的话就创建失败了呢
        (void)n;//毫无意义的使用一下n，要不然好像会报警告，之后可以实验一下

        //1.2创建子进程
        pid_t id = fork();//fork后，子进程返回值为0，父进程返回值为子进程的id值
        assert(id != -1);//返回-1为报错

        //返回值为0，则为子进程
        if(id == 0)
        {
            cout<<getpid()<<"子进程关闭父进程对应的写端： ";
            for(auto& fd: fds)
            {
                cout<<fd<<" ";
                close(fd);
            }
           
            cout<<endl;
            //pipefd[0]为读，pipefd[1]为写，子进程此次需要读，不需要写。
            //管道文件是半双工通信，对于一个pipe文件，pipe文件创建时，会有两个文件描述符，
            //父进程和子进程通信时都必须关闭一个。
            close(pipefd[1]);//关闭子进程pipe文件的写描述符
            //输入重定向
            dup2(pipefd[0],0);//dup2函数是重定向，将pipefd[0]中的内容复制到0号文件描述符中
            //子进程等待执行命令
            WaitCommand();
            //执行完毕后，关闭文件读文件描述符
            close(pipefd[0]);
            //退出子程序
            exit(0);
        }

        //父进程的操作
        //1.3关闭父进程的读fd
        close(pipefd[0]);

        //将子进程的id和父进程的写入fd存入。
        end_points->push_back(EndPoint(id,pipefd[1]));
        fds.push_back(pipefd[1]);

    }
}

int Showboard()
{
    
    cout<<"*******************************"<<endl;
    cout<<"** 0.打印日志    1.数据库任务 **"<<endl;
    cout<<"** 2.网络请求    3.quit退出   **"<<endl;
    cout<<"*******************************"<<endl;
    int command = 0;
    cin>>command;
    return command;

}

void Start_Process(const vector<EndPoint> &end_points)
{
    int cnt = 0;
    int indix = 0;
    while(1)
    {
        //1、选择任务
        sleep(1);
        int command = Showboard();
        if(command == 5) break;
        if(command <0 || command >5) continue;
        
        //2、选择进程，此处依次进行
        indix = cnt++;
        cnt %= end_points.size();
        string name = end_points[indix].processname;
        cout<<"你选择了进程："<<name<<"  | 处理任务："<< command<<endl;

        //3、下发任务
        write(end_points[indix]._write_fd,&command,sizeof(command));

    }
}

//这里存在很大的一个问题，不过在CreateProcess中已经解决了
//就是子进程会复制父进程的旧的文件描述符
void WaitProcess(const vector<EndPoint> &end_points)
{
    //要让子进程退出，只需关闭父进程的写文件描述符，因为子进程
    for(int end = 0;end<end_points.size();end++)
    {
        cout<<"父进程让子进程退出："<<end_points[end]._child_pid<<endl;
        close(end_points[end]._write_fd);

        waitpid(end_points[end]._child_pid,nullptr,0);
        cout<<"父进程回收了子进程："<<end_points[end]._child_pid<<endl;

    }
}


int main()
{
    //1、用vector数组存储每一个子进程的id和写入的文件描述符fd
    vector<EndPoint> end_points;
    //创建子进程
    CreateProcess(&end_points);//得到了end_points

    sleep(1);

    //执行进程
    Start_Process(end_points);

    //处理退出的回收问题
    WaitProcess(end_points);
  
    return 0;
}